Phase 1: The Data Layer (Val Town) Goal: Create a single, reliable Val Town API endpoint that provides all the necessary instance data, properly formatted and cached.
Task 1.1: Research the XIVAPI /instancecontent endpoint.
Familiarize yourself with its structure. You will need to request specific columns to get the data required by the PRD.
Identify the fields for:
Name: The instance's name.
ContentType.Name: This will give you the "Type" (e.g., "Dungeon").
ContentFinderCondition.Name: This often contains the common name for the duty.
Unlock Quest: This can be tricky. Look for a field like QuestUnlock which might be an object containing the quest name. You may need to experiment to find the most reliable field.
Note on Pagination: XIVAPI results are paginated (e.g., 100 results per page). Your script must loop through all pages to collect every instance.
Task 1.2: Create the Val Town "Val" (Function).
Create a new val in your Val Town workspace (e.g., ffxivInstanceList).
Write JavaScript code inside this val to perform the following logic:
Initialize an empty array, e.g., allInstances = [].
Make an initial fetch call to https://xivapi.com/instancecontent?columns=ID,Name,ContentType.Name,....
Write a while or for loop that continues as long as the API response indicates there is a next page (Pagination.PageNext != null). In each loop, fetch the next page and add the results to your allInstances array.
Once all instances are collected, process the array: Group the instances by their expansion. You may need another API call to /expansion or hardcode the expansion order and level caps to achieve this grouping.
Return the final, grouped JSON object.
Task 1.3: Add Caching to the Val.
Instance data changes very infrequently (only with major patches). To avoid hitting XIVAPI rate limits and to make your app load instantly, add caching.
A simple approach in Val Town is to store the result and a timestamp.
Modify your val: before fetching, check if a cached result exists and if it's recent (e.g., less than 24 hours old). If it is, return the cached data immediately. If not, perform the fetch, then update the cache with the new data and timestamp.
At the end of this phase, you will have a URL that returns a clean JSON object of all game instances, grouped by expansion.
Phase 2: The Frontend Structure (HTML & CSS) Goal: Create the static skeleton of your web application.
Task 2.1: Create index.html.
Set up a basic HTML5 document.
In the , add a (e.g., "FFXIV Completion Tracker"), and link to your style.css and script.js files.
In the <body>, create the main layout:
A
containing anfor the title and an .
A
element. This empty div is where your JavaScript will render the list.Task 2.2: Create style.css.
Write some basic CSS to make the app usable from the start.
Style the body for a nice background color and font.
Center the main content and give it a max-width.
Style the header and search bar.
Create CSS classes for the elements you will generate with JavaScript:
.expansion-group { ... } (for the container of an expansion's instances)
.expansion-header { ... } (for the "Heavensward" title)
.instance-row { display: grid; grid-template-columns: 3fr 1fr 1fr 1fr; ... } (to align the name and checkboxes nicely)
.checkbox-label { ... }
At the end of this phase, you will have a static webpage with a title, a search bar, and an empty area, all styled and ready for data.
Phase 3: Interactive Logic (JavaScript) Goal: Use JavaScript to connect the data to the UI and implement all user-facing features.
Task 3.1: Fetch and Render Data.
In script.js, write an async function to fetch data from your Val Town URL.
Once you have the JSON data, write a render(data) function.
This function will:
Get the
element.Clear any existing content inside it.
Loop through your grouped data (e.g., for each expansion).
For each expansion, create and append an
header.
Then, loop through the instances within that expansion. For each instance, dynamically create an .instance-row div containing the Name, Type, Quest, and the two checkboxes with labels. Crucially, give each checkbox a unique id or data-id attribute based on the instance ID from the API (e.g., data-instance-id="${instance.ID}").
Task 3.2: Implement State Persistence.
Saving: Create a saveState() function. This function will find all checkboxes on the page, loop through them, and build a JSON object like { "instance_id_123": { unlocked: true, completed: false }, ... }. It will then save this object to localStorage.setItem('ffxivCompletionState', JSON.stringify(stateObject)).
Loading: Create a loadState() function. This function will get the item from localStorage, parse it, and then loop through the rendered checkboxes on the page, setting their checked property to true or false based on the loaded data.
Binding: Call loadState() after you first render the list. Add an event listener to the main instance-container that listens for change events on the checkboxes. When an event occurs, call saveState().
Task 3.3: Implement Search/Filter.
Add an input event listener to the #search-bar.
Inside the listener function, get the search bar's current value (and convert to lowercase).
Get all the .instance-row elements.
Loop through them. For each row, check if its text content (in lowercase) includes the search term.
If it matches, set row.style.display = ''. If not, set row.style.display = 'none'.
At the end of this phase, your application will be fully functional on Val town.
// July 20 2025
Update all titles so that they follow APA titlecase style.
Make all text within each row to adopt a body style and unbolded styling.
Center align all columns.
Set a max width of 1200px for the page content except the header.