The goal of this proposal is to discuss the alternatives to implement support for row creation to Live Data. This is part of a general process to improve Live Data usability and uses, but this specific proposal does not cover the livedata macro itself nor AWM, only row creation.
Also, while I will be using the terms rows and columns a lot, everything mentioned also applies to the card layout (so, cards and fields).
Right now, a lot of the requirements to support this process are already implemented in Live Data:
the current layouts already include a Add entry button, though it is disabled.
the current REST endpoints already support the POST request to add a new entry, and it just needs to be actually implemented in the livetable source.
As such, this proposal will focus on the addition process for the user. On that front, there are two main open questions for the livetable source: where to store the new data, and how to display it to the user once it has been created.
Page Creation
The data of the new entry will need to be stored in a page. There are a few alternatives I can think of when the user clicks the Add entry button:
Ask the user for the full page location for each new row
Ask the user only for the page name for each new row
Use the current space
Use a configured space in the livedata macro
Ask for the space for the first row and keep using the same for the following ones
Do not ask anything and auto-generate page names
Use the current space
Use a configured space in the livedata macro
Ask for the space for the first row and keep using the same for the following ones
Personally, I vote for option 2.1 (which is also the current behavior of AWM) since itâs the most straightforward and intuitive to me. Option 2.2 could also be optionally implemented to cover most of the other use-cases.
There is also the question of how to ask the user for this information:
Always open a popup at the very start of the row creation, with the field auto-focused
Use appropriate columns if the Live Data displays them (e.g., doc.location and doc.title, the location is usually not editable but there could be an exception during the row creation process)
Enter a ârow creation modeâ that would add special editable columns to support this use-case.
Option 2 seems to be a decent compromise here, there is a slight overhead when the popup opens but I expect most of Live Data uses to display the page title.
Display Update
A Live Data has pagination and filters. This means that, once a user has created a new row, it might not be displayed because it is not part of the currently displayed page or itâs filtered out. There are a few ways to handle this when a new row is validated:
Refresh the whole table, disable filters and move to the page containing the new row
Ignore page limits and filters and simply keep displaying all the new rows until refresh, page switch or filter update
Do nothing, once created the new data will simply disappear if not all conditions are met
I like option 2 more, I believe itâs the less distracting one when quickly inputting new rows in bulk.
Creation process proposal
We want a process that can be done entirely through keyboard once the creation button has been clicked once.
Assuming the chosen options are 2.1, 2, 2, an ideal creation process could be:
Click the row creation button (Add entry)
Depending on whether or not the Live Data displays doc.title, either make it editable or open a popup
The new row appears and the first editable column is opened in edit mode and focused
Tab and Shift+Tab can cycle between the columns. If the editor for the current cell displays a list, the list elements must first be cycled through to reach the other columns
The new row will be created once validated. This can be done through a few different actions:
Cycling with Tab until the user exists the row and reaches the creation button again
Pressing Return
Pressing Ctrl+Return, which will also create a new row and loop back to step 1
What do you think? Feel free to suggest any other alternatives I might have missed and/or ask for clarifications.
We want the feature to be easy to use (asking anything makes it just too hard)
Use the current space doesnât work in the majority of cases. The argument of current AWM behavior doesnât work anymore because weâre talking about LD that are in-situ and not located in any app dedicated for something.
We need the location to be configurable and having it in the LD config makes the most sense to me. We would still not have the use case of being able to add new pages in various spaces in the wiki but I donât see a real use case for this and if we need that, we can always add a new strategy.
In term of design, maybe make the strategy configurable.
PS: We donât need to autogenerate the page name fully. The LD config should provide a template for the name (with a fixed part and the rest being a generated number for ex - will require checking existing values but I think itâs nicer and something we want).
So first we need to define who can create new rows (probably users who have edit rights in the target space).
I would put a âAdd Entryâ button but a âSwitch to edit modeâ one. When you click it, the LD goes in edit mode showing cells and basically becoming like excel, and you can create a new line using the table without clicking any button.
Iâd put the âSwitch to edit modeâ button in the LD kebab menu so that itâs not too visible as itâs not the main use case for LDs.
While I agree that we should make it easier to add new entries, Iâm wondering if adding entries inline in the table (Excel-like) is really the best way:
The Live Data might not display all columns, in fact it might miss mandatory columns
The space in a Live Data column is very cramped. Iâm not sure that we can fit there inline validation messages, hints that transmit important information and a full WYSIWYG editor (WYSIWYG editing is currently unsupported in Live Data)
What I would find more realistic is to implement proper support for adding new entries in a modal. We would need to revamp our modal support to support stacking modals, but that seems like a good idea, anyway. In the modal, we would have enough space to have a proper edit form with hints and validation and there would be a clear flow for saving an entry. I think it would still be a much more straightforward experience than the current one.
What about devices that donât have an always-visible keyboard, like phones or tablets? How would you validate a new entry there? How can you avoid validating too early because the user moves the focus outside the new row to hide the keyboard? How can users learn about the keyboard navigation, in particular users who arenât power users and thus not used to using Tab or Return to validate data/move to the next field?
Note that live data is generic and doesnât have the notion of pages. It knows only about entries which have properties. A live data source may fetch its data from wiki pages, but the live data UI shouldnât be aware of that. This means the live data UI cannot ask for a page name. The location where the data will be saved depends on the data source. Thus, you either donât ask for the location and let the live data source save the new data where it needs to, or you allow the live data source to provide a location picker UI, through some UI extension. I think the first option is fine for now, i.e. to let the live data source decide the location based on some live data source parameters, which Vincent suggested as well.
Same, but I think it would be nice to also provide some visual indication that the filters and sort are not fully applied, and some refresh / update action to enforce the filters (after the user has finished adding rows).
The 3 options you listed may be fine for advanced users, but for simple users I think you need to:
either (attempt to) save whenever a cell loses the focus (i.e. prepare for realtime editing)
or have a dedicated save action somewhere in the UI, for users to click after they fill all the cells
The problem with save on blur is that in some cases you wonât be able to save partial data due to validation (e.g. some properties are mandatory, or the value of one property depends on another property, like country â city).
The dedicated save action could be the switch between view and edit mode that Vincent suggested.
Hi @pjeanjean, thanks for moving forward with this implementation. For me:
As I was answering this I took a look at @vmassol and @mflorea takes on this and I guess I was understanding it wrong. I will abstain for now.
Option 2 would be my choice as well. 1 and 3 are bad because option 1 would be taking control away from the user and option 3 would not be showing relevant status update.
Agreed on the proposed process. Ideally we end in a state where almost everything can be done in the table itself for creating the entries. My only point is that while it can be used entirely via keyboard, this should not be the only option.
When adding in the table view weâd need to show all mandatory columns. Now this brings a situation in which we have some columns available for viewing and others when editing, depending on the table this might be a nightmare to use. I donât have an ideal solution for this.
I understand your position, but this means that we would switch the user away from the context of the table. This means that the user must have in mind what was already on the table beneath the modal, and if they forget, they must close the modal to check, and this modal could already be half filled.
Just my opinion now, but it would be weird for small tables as well.
For the first iteration I wouldnât add this. We could end up putting too much information in the UI from the get go. If new data is not fully sorted, and someone notices this, the first action of the user will be to do some sort of refresh/reset of the page to check and then theyâll learn about the behavior. If users request it, then we could add something to indicate.
When the user switches to edit mode, we could display all columns, including the hidden ones but mark them visually as hidden (so that the user understands they wonât be visible in view mode).
I also prefer to stay in table editing mode, a la Excel. More like what @mflorea suggested in having a simple editing experience, and a more advanced view for complex cases (if thatâs needed in the future).
Just to be sure: are we talking about the fields declared in the Live Data macro, or all the fields defined in the XClass? Because right now I donât think Live Data offers a generic way to obtain the full list of available fields for a given source (i.e., fields of the XClass for livetable).
So, the first one could be done (if we trust the user of the Live Data macro to have declared all of the important columns) but the second one would be a lot more complex.
I was indeed thinking about the fields defined in the LD macro (including the hidden ones).
For fields not defined in the LD, I donât know what we should do. Either donât edit them at all through the LD since theyâre not meant to be displayed or have an option to show them (I wouldnât show them by default though).
Are you sure about this? I thought I had read somewhere that if we donât define the fields, the LD was going to use them all. In any case, it wouldnât be too hard to add a feature to display all fields as theyâre easily discoverable through the XClass reference.
Alright, thanks everyone for your answers, now to try and summarize everything:
For the storage, we should provide alternatives but right now the most important one should be to have automatic page names with a configured space (3.2), ideally through a template parameter. Note that this setting should be a source parameter to keep Live Data generic.
For the display, we should always keep the new rows displayed (2) but consider improving the UI to properly convey that the filters/pagination are temporarily disabled.
For the creation process, we should definitely start by considering an âeditâ mode that would display all columns listed in the Live Data macro call. It would only be accessible if the user has the proper edit rights. From that, we could keep the process described in the first message, but also add an explicit âsubmit/saveâ button for non-power users. We can look into an optional âmodal inputâ support later, and also make it default for mobiles.
I would consider highly structured data out of scope for now. And, I would optimize for the quick insertion of entries with loose data constraints (i.e., entries can be empty and do not depend on each other).
If what is displayed in a given LD is a partial view of complex data, itâs a perfectly valid use case, but allowing the in-place insertion of data from the LD might is not relevant and should be disabled.
In the same spirit, the name of the location where the new entry is saved (be it a page name or something else), must be computed automatically by the source.
At this point, this does not support disabling the filters/pagination, and the only naming strategy implemented uses UUIDs for page names (with a configurable space).
Other than that, it does implement the edit mode and the full keyboard input process, so feel free to check it to make sure we all agree on the look and feel.
@pjeanjean Iâve asked my LLM to review it. Note that Iâve read it but havenât analyzed it in details (no time ATM), but I thought maybe there could be something useful in it for you. Iâd be interested if you could tell if thereâs anything useful or not in that automated review. Thx.
The interaction skeleton is right and the generic/source separation is respected (the load-bearing constraint mflorea raised). This is a strong foundation. But three deliberate product decisions from the accepted design were dropped or weakened in the shipped code, and each one hits the featureâs own stated goal (âquickly add entries in bulkâ). As shipped, a casual user adding a row can, in one flow: be allowed into edit mode without edit rights, lose sight of the row they just saved, land on an unreadable UUID page name, and unknowingly create an invalid entry â four separate âdid this even work?â moments.
Recommendation: do not treat this as feature-complete for XWIKI-17627. Address the two P0 correctness/UX items below before this is presented to end users; frame UUID naming as an explicitly-temporary v1.
Explicitly deferred; saved row disappears under pagination.
Visual hint that filters/sort are suspended for new rows
No
Deferred (depends on the item above).
2. P0 â Block / immediate fast-follow
These are where multiple lenses converged independently.
P0-1 ¡ Saved rows vanish under pagination/filters â defeats the headline use case
The feature is sold as bulk add; the design reached consensus on option 2: keep new rows visible even if filtered/paginated out. The PR does not do this â saveNewEntry() splices the draft and calls updateEntries() (a re-fetch); the saved row only reappears if it lands on the current page and passes current filters/sort. On a 15-row table, add row #16 â save â it disappears. To a non-technical user this reads as âmy entry was lost.â The draft-retention plumbing is already half-built (updateEntries() re-injects _new drafts), so the missing piece is pinning saved rows. Lenses: usability (P0), UI/UX (#9), API (the client discards the 201 body that contains the created entry, then re-queries â returning Promise<Entry> would let the UI keep/locate the row deterministically). LiveDataLogic.tssaveNewEntry() / updateEntries() re-injection (diff ~685â700, ~772â793); XWikiLiveDataSource.addEntry returns Promise<void> (diff 845â854).
P0-2 ¡ Edit affordance shown to users who canât save â guaranteed permission-error dead-end
Design: the edit-mode checkbox should appear only if the user has edit rights. The PR shows it purely from source.hasEditMode == "true"; enableEditMode() just flips a ref, and the seeded draft hardcodes doc.hasedit: "true". A read-only user can enable edit mode, type a whole row, hit save â and only then hit the server-side Right.EDIT check, losing their input. View-but-not-edit is the common case for shared dashboards, so this is high-frequency. Security is not breached (backend fails closed in ModelBridge.updateAll), but the UX is a dead-end. Gate the toggle/Add-row on an actual edit-rights signal. Lenses: usability (P0-2), architecture (Medium), UI/UX. LiveDataLogic.tshasEditMode() (diff ~768â770), addEntry() hardcoded doc.hasedit (~734â738); LivedataDropdownMenu.vue (~312â322).
P0-3 ¡ Save failure is silent â no user-facing feedback
saveNewEntry()'s catch does console.error(...) + re-throw. No XWiki.widgets.Notification, no banner, no inline message â and on error the draft row is not spliced, so it lingers in an ambiguous state. Compare DisplayerXObjectProperty.vue, which does surface a Notification. Combined with P0-2 (permission errors) and missing-location errors, this means the most likely failure paths are all invisible to the user. Lens: UI/UX (#1, Critical).LiveDataLogic.ts (diff 783â791).
P0-4 ¡ Double-save race between onDisplayerBlur and onEnter
The old code debounced view-switching with setTimeout(âŚ, 200). The new onDisplayerBlur calls await applyEdit() immediately, while onEnteralso calls applyEdit() then saveNewEntry(). Pressing Return (or, on mobile, tapping Save which blurs the input first) can fire both, double-invoking save on an already-removed draft. No isApplying guard. Lens: UI/UX (#2, Critical); mobile manifestation in UI/UX #11.BaseDisplayer.vue (diff 441â500).
3. P1 â Should fix before GA
P1-1 ¡ UUID-only page names are a usability regression vs AWM, and contradict the ânicerâ goal
Vincent explicitly preferred a prefix + auto-incremented counter template and called it ânicerâ; the PR ships uuid-only (Space.<random-uuid>) and throws on any other strategy. Impact is concrete and permanent (pages donât rename easily): unreadable URLs/breadcrumbs, page tree full of opaque siblings, navigation-by-guesswork â worse than todayâs AWM. Acceptable as a v1 only if framed as temporary with the template strategy committed as a fast-follow. The bigger risk is shipping uuid, marking the issue done, and never returning. Lenses: usability (P0 as a decision), architecture (see P1-2).LiveTableLiveDataEntryStore.generateNewEntryReference() (diff 106â132).
P1-2 ¡ Naming strategy is a hardcoded switch, not a pluggable extension point
generateNewEntryReference() is a switch (namingStrategy) with a single case "uuid"; the blank-location check and exists-collision logic are baked into that case. This contradicts the accepted designâs âconfigurable/pluggable strategyâ and the XWiki-idiomatic approach (a @Role strategy component with @Named("uuid") hints, looked up by the source-param hint). Every new strategy currently requires editing this method and recompiling. Worth extracting now, while the surface is new/@Unstable â retrofitting post-release is a breaking change. (Note: the case null, default: label is fine â the project is on Java 21 where JEP 441 finalized null switch labels; verified, not a compile issue.) Lens: architecture (High).
P1-3 ¡ save() return-value change couples to get(entryId) round-trip and the REST Location header
save() now returns the local-serializedDocumentReference instead of the callerâs raw fullName. The API lens confirms this is actually more consistent with the doc.fullName idProperty format the frontend already uses (livetable emits local Space.Page), so itâs largely benign â but it is an observable, undocumented behavior change, and DefaultLiveDataEntriesResource.addEntry immediately feeds it into get(entryId) to build the Location header. Add a test asserting get(save(entry)) round-trips for both create and update, and watch the cross-wiki newRowLocation case (the local serializer drops the wiki prefix). Lenses: architecture (High), API (Low â downgraded after confirming format match).LiveTableLiveDataEntryStore.java (diff 56â68, 97); DefaultLiveDataEntriesResource.java:86â108.
P1-4 ¡ Mandatory XClass fields not in the macro â silently broken entries
Edit mode shows all macro-declared columns only. Mandatory XClass properties not declared in the macro canât be filled inline, and nothing validates or warns â the user saves a âcompleteâ row thatâs actually missing required data, failing later in a far-away view. This is precisely the structured-data case mleduc declared out of scope, but the PR doesnât guard the boundary, it silently produces invalid data. At minimum: detect mandatory-but-unfillable fields and block inline-add (or warn) for that table. Lens: usability (P1).LiveDataLogic.tsisPropertyVisible() (diff ~744â747).
P1-5 ¡ Hidden columns shown in edit mode but not marked
Design said mark them as hidden so the user knows they wonât show in view mode. The PR reveals them (visible || editMode) with no badge/styling/tooltip â âwhy did my column disappear after saving?â Cheap to fix. Lenses: usability (P1), UI/UX (#3).LiveDataLogic.ts (diff ~747).
P1-6 ¡ Tab-to-cycle-cells not implemented (design step)
The designâs keyboard sequence requires Tab to move between editable cells (and cycle list editors, switching columns only on first/last element). No @keydown.tab handler exists; the PR relies on native focus order + onDisplayerFocusâs setTimeout(200) to re-open the editor â a race, not the specified behavior. Lens: UI/UX (#5, #8).BaseDisplayer.vue (diff 441â447).
4. P2 â Polish / smaller correctness
Accessibility cluster (UI/UX #4, #6, #7): checkbox nested inside <a href="#"> is invalid HTML and double-fires the toggle (the @click.stop on input + @click.stop.prevent on anchor cancel each other) â use <li role="menuitemcheckbox"> + <label>; icon-only Save/Cancel rely on title (not a reliable accessible name) â add aria-label/visible text; the âActionsâ <th> and empty filter <th> lack scope/id/headers association.
_new draft model fragility (architecture, Medium): re-injection of drafts captured before the await in updateEntries() can drop/duplicate a draft created during an in-flight fetch; the whole model implicitly assumes one draft (findIndex(_new) everywhere) but addEntry() pushes unconditionally; drafts live only in data.data.entries, so a refresh/layout-switch outside updateEntries loses them silently. Document and guard these invariants.
Edit-mode state duplicated (architecture + UI/UX 12): truth lives in LiveDataLogic.editMode (ref) and the dropdownâs local data.isEditMode; the checkbox binds the local copy. Bind directly to logic.isEditMode(), single source of truth. Also beforeUnmount disables edit mode but doesnât cancelNewEntry() â a _new row can persist with no active edit mode.
Source-parameter contract is implicit/stringly-typed/undocumented (API, Medium):hasEditMode/newRowNamingStrategy/newRowLocation arenât in any schema or macro doc; hasEditMode == "true" rejects boolean/1; missing newRowLocation throws only at save time (validate it when the addEntry action is injected in the resolver instead). The addEntry action descriptor is injected from newRowNamingStrategy != null, decoupled from hasEditMode â a config can be half-functional until a runtime error.
addEntry added as a non-optional method on the @betaLiveDataSource TS interface (API, Medium): safe in-repo (only XWikiLiveDataSource implements it, updated here), but it breaks any downstream implementor â call it out in release notes or make it optional until stable.
Mobile/touch (UI/UX #11, raised by Michael Hamann): blur-before-tap races on the on-screen keyboard; edit mode is only discoverable via a desktop kebab. Unaddressed.
@since/@Unstable inconsistency (API, Low):exists() is tagged, the new 6-arg updateAll overload isnât; its @see still points at the 5-arg variant, and it now does create and update (SRP smell). Pick one convention (the package is internal, so arguably neither needs @Unstable).
5. Testing gaps
No unit test for the create branch (generateNewEntryReference): uuid happy path, missing newRowLocation â exception, unknown strategy â exception, candidate-already-exists â exception. ModelBridgeTest was only updated mechanically to pass create=false; nothing exercises create=true.
No test that save()'s new return value round-trips through get(entryId) for create and update.
No test for the resolverâs conditional injection of the addEntry action based on newRowNamingStrategy.
No end-to-end test of the TS addEntry path, in particular the case where the created row falls outside the current page/filter and disappears after save (P0-1).
6. Whatâs genuinely good (keep)
Generic/source separation honored â addEntry(source, values) leaks no page/location into generic code; verified no page/space knowledge in livedata-ui/livedata-api. This was the hardest constraint and itâs respected.
Toggle in kebab menu + explicit Save/Cancel + Ctrl+Enter bulk loop â the right interaction primitives, and the explicit buttons correctly answer the non-power-user concern from the thread.
Add action gated on the source declaring a naming strategy â a source with no storage policy simply offers no add. Clean architecture-as-product.
Backward-compatible Java additions â the 5-arg updateAll still delegates (create=false); get(entryId) going from UnsupportedOperationException to a real impl is a strict capability gain.
Generated from a four-lens parallel review of PR #5639 against the accepted design and forum thread #18412.
When Pierre made me a demo of his work, the strategy was a template with an incremental suffix.
He raised the following limitations:
Finding the next value can be expensive since the code has to load all potential pages and scan them in order to find the next unassigned value (or do we want to go for max+1)
It can be tricky to implement in a way that is concurrency-safe (and cluster-safe).
So my understanding he that he choose to move to a stronger UUID based implementation, but the PR and forum post lacks explanations.
I understood that part but it seems to me that itâs going to create some mess to use the feature. When you go to the Page Index, Navigation and other places and more generally when you do some admin work on your wiki and you see these page names, you wonât know what they are about until you open them.
Itâs going to be fine in all places that use titles but Iâm not sure this covers all use cases.
So Iâd like to discuss the idea of an optional prefix at least (even if an UUID is used after the prefix). TBH I donât know if that fully helps or not but I think it could help filter in/out when you have a list of page names (like in a LD).
In any case, I havenât checked the PR but the strategy needs to be configurable at the livedata macro level. And one impl should be about asking the user to enter the page name.