GSoC Week 1: When Small Frontend Bugs Exposed Bigger API Questions

My first coding week on VideoLAN (VLC) CrashDragon was not about writing a big feature. It was about understanding what the current Vue frontend actually depends on.
CrashDragon already has multiple backend surfaces: public routes, authenticated routes, admin routes, and /api/v1. So from far away, the task can sound like an endpoint migration problem.
But once I started running the app locally with real demo data, it became less about which route exists and more about which route gives this page the contract it needs.
The Bug That Made It Concrete
The crash list pagination was the first issue that made this clear. The current list flow is based on limit and offset.
In simple terms, limit says how many rows the frontend wants, and offset says how many rows to skip before returning the next page. So page 1 might ask for 10 rows with offset 0, page 2 asks for 10 rows with offset 10, page 3 asks for 10 rows with offset 20, and so on.
The Vue page already had a limit value, but the service call only sent:
/crashes?offset=${offset}So the frontend thought it was asking for 10 crashes, but the backend used its default limit instead. The request needed to include the limit too:
/crashes?limit=${limit}&offset=${offset}That looked like a small frontend fix at first.
But there was a second problem. The frontend was also using the current page length as the total count:
ItemCount: crashes.length
That is not really a total count. It only says how many rows came back in this response. So the current fix became both frontend and backend:
- Send limit from Vue.
- Return
Items,ItemCount,Limit, andOffsetfrom the backend. - Use the backend count instead of the current page length.
This became MR !44. That fixes the current crash list pagination mismatch, but it also opened a bigger design question.
Offset pagination is simple and works well as an initial model. It is easy to understand, easy to wire into the frontend, and good enough for many list pages.
But for larger crash or report tables, offset pagination can become more expensive. If the user asks for later results, the database may still need to walk past many earlier rows before returning the next set. It can also become less stable when new crashes or reports are inserted while someone is paging through the list, because the meaning of "skip the first N rows" can shift as new rows arrive.
That is where keyset pagination becomes interesting.
Instead of saying:
give me 10 rows after skipping 20 rows
Keyset pagination says something closer to:
give me the next 10 rows after this last seen item
For example, if the list is ordered by created_at and id, the frontend can send a cursor based on the last row it received. The backend can then continue from that known position.
That is usually more scalable because the database can use the ordering key directly instead of repeatedly skipping over earlier rows. It can also make append-heavy data, like crashes and reports, more stable while paging.
The tradeoff is that the frontend and backend need to agree on cursor semantics instead of only limit and offset. So this should not be mixed into the immediate bug fix.
I split the work into two tracks:
- #63 Crash list pagination uses missing limit and page-length count.
- #64 Migrate offset to keyset pagination for crash and report lists.
That separation matters because the first one is a focused bug fix, while the second one is an API design decision.
Tracing the Full Path
This was the pattern I kept coming back to during Week 1:
Looking at only one layer was not enough.
Postman showed response shapes. Backend code showed what handlers were doing. The browser showed what actually happened when a user clicked something. The bugs were hiding between those layers.
Auth Was Another Small Signal
The fixed-crash button was another example.
The backend route for marking a crash as fixed is authenticated. The frontend showed the button to a logged-in user, but the request did not send the auth header, so it returned 401.
That became #65 Fixed crash toggle misses auth header, and MR !43 fixes that small issue.
But while checking nearby code, I noticed a larger pattern: authenticated frontend calls are currently handled in more than one way. Some code uses axiosClient, while some public-route actions still use plain axios and pass the auth header manually.
That does not need to be solved inside a small MR, but it is a good follow-up discussion: should authenticated Vue requests move to one shared Axios auth setup or interceptor?
Comments Were a Contract Mismatch
Crash comments showed a different kind of mismatch.
The Vue side sends JSON: { comment }. The backend handler reads form data: c.PostForm("comment").
Both choices make sense in different contexts. Form data fits older server-rendered pages. JSON fits the Vue frontend.
The question is what contract CrashDragon should support going forward. Should Vue send form data, should the backend also accept JSON, or should both paths continue to work?
That became #66 Crash comment submission does not match backend form handler instead of being mixed into an unrelated MR.
When a Dropdown Lies
The product/version dropdowns were also interesting.
The navbar selection changes visually, and the selected values are stored in frontend state. But the crash, report, and symfile pages do not currently reload data from that selection.
On the backend side, public handlers still filter through product/version cookies. So the UI can show one product/version while the page data still belongs to another.
This was only obvious after adding more than one product/version locally. That turned the issue from "dropdown exists" into a state ownership question:
- Should Vue write backend cookies?
- Should filters become query params?
- Should pages listen and refetch?
- Should the selectors be hidden until they are actually wired?
That became #69 Product/version filters do not update page data.
Public Routes vs /api/v1
One important lesson from the audit was that /api/v1 is not always a drop-in replacement yet.
For list endpoints, /api/v1 often has a cleaner envelope. But for some detail pages, public routes currently return more page-ready data.
For example, the public crash detail route returns reports, comments, version stats, OS stats, pagination fields, and the crash itself. The /api/v1/crashes/:id route is cleaner, but it only returns the crash item.
So the migration cannot just be "replace public routes with /api/v1 everywhere." It needs to happen one flow at a time.
Placeholder Pages Are Also Part of the API Story
Stats and symfiles also came up during the audit.
The Vue pages exist, but they are still placeholders. That makes them easy to ignore during route mapping, but they still matter because they represent missing frontend surfaces.
For stats, the backend already has useful data that can drive a real page. For symfiles, the backend route exists too, but the frontend needs to decide how to show product/version context around that data.
These became separate issues:
- #67 Symfiles page is still a placeholder.
- #68 Stats page is still a placeholder.
Keeping these separate helps avoid mixing frontend completion work with API contract bugs.
Turning Findings Into Separate Threads
By the end of the week, the findings were no longer one vague "frontend/API cleanup" task. They had split into different kinds of work:
- Current-flow bugs, like crash pagination and fixed-crash auth.
- Contract mismatches, like comments using JSON on one side and form data on the other.
- Missing frontend surfaces, like stats and symfiles.
- State ownership issues, like product/version filters.
- Design questions, like keyset pagination and shared Axios auth handling.
The main threads were:
- #63 Crash list pagination uses missing limit and page-length count.
- #64 Migrate offset to keyset pagination for crash/report lists.
- #65 Fixed crash toggle misses auth header.
- #66 Crash comment submission does not match backend form handler.
- #67 Symfiles page is still a placeholder.
- #68 Stats page is still a placeholder.
- #69 Product/version filters do not update page data.
That separation helped keep the first MRs small. MR !43 fixes the fixed-crash auth issue. MR !44 fixes the crash list pagination mismatch. Neither MR tries to solve the whole API migration, and that is intentional.
What Changed In My Understanding
The main thing I learned this week is that frontend/API migration is not endpoint replacement. It is contract verification.
A route can exist and still not give a page enough data. A frontend control can exist and still not drive backend filtering. A small bug can point to a larger design question.
So my current approach is:
- Run the page with real data.
- Trace the Vue service call.
- Compare the public route and
/api/v1response. - Check the actual UI behavior.
- Open a focused issue.
- Make a small MR only when the fix is clear.
That is slower than blindly changing endpoints, but much safer.
Open Threads For Week 2
The main questions I want to continue with are:
- Which frontend flow should move toward
/api/v1first? - How should crash/report lists handle keyset pagination?
- Should authenticated Vue actions use one shared Axios auth client?
- Should comment handlers support JSON as well as form data?
- How should product/version filter state drive page data?
- What should the stats and symfiles pages show in the Vue frontend?
Acknowledgement
Thanks to epirat and the VLC Team for the guidance and review during this first week. The discussions around API direction, pagination, and frontend behavior helped me avoid treating this as a simple endpoint replacement task and pushed me to look more carefully at the actual contracts between the backend and the Vue frontend.
Closing
Week 1 did not solve every API/frontend question, and it was not supposed to. The useful outcome is that the messy part is now visible: which bugs are small fixes, which ones are contract mismatches, and which ones need design discussion before implementation.
That feels like the right place to start Week 2.