A handoff can be confusing even when every file opens. The person receiving it may not know which artifact is editable, which one is the intended image export, or which garment the image was prepared for. A small delivery note can make those declarations explicit. The important engineering distinction is that checking a note is not the same thing as checking the files it describes. A consistent record can describe a nonexistent file, an unsuitable design, or an image with entirely different contents.
This tutorial builds a small, dependency-free JavaScript function for checking that kind of declaration. It does not open images, inspect a filesystem, send a network request, or call a product API. Sixteen local fixture tests exercise the function's behavior. Those tests demonstrate the implementation's handling of those fixtures, not Roblox compatibility, image integrity, permission to use artwork, or the correctness of a real exported garment. That boundary is part of the interface rather than a qualification hidden after a success message.
The motivating context is a browser clothing workflow. I am associated with Roblox Shirt Maker, available at https://rbxshirtmaker.com/ . Its studio provides templates, PNG import, text, colors, layers, an R6 preview, project saving and PNG download. The core manual workflow is presented as free; optional AI generation requires an account and credits. The delivery-note function here is a separate educational utility. It is not an official product schema, an integrated feature, an official Roblox validator, or a claim that a product export was tested.
Define the claim before writing the predicate
Start by choosing a modest result name. Here the result is consistent, not validPng, readyToUpload or approved. That vocabulary matters because a boolean tends to travel farther than the explanation around it. A later script or interface can easily display a reassuring green badge without carrying the original caveats. Naming the result after the narrow question makes that kind of accidental promotion less tempting, although documentation and caller behavior still need review.
The note declares a garment kind, a PNG format discriminator, a canvas size and two artifact labels. Classic shirts and pants use the appropriate 585 by 559 layouts in the workflow described here; graphic T-shirts use transparent 512 by 512 images. These numbers define the sample contract, not a universal image-validation algorithm. In particular, matching dimensions cannot distinguish a shirt layout from a pants layout or tell you whether a graphic has meaningful transparent pixels.
An editable artifact and an exported image also have different jobs. The editable label reminds a recipient which item retains the working decisions. The export label identifies the intended flattened delivery image. The example uses an illustrative .project suffix for the editable label; that is not a claim about the website's project-file extension or structure. No decoder is inferred from the suffix, and the function never attempts to interpret either artifact.
Keep the input shape deliberately small
Use ordinary parsed JSON as input, not arbitrary JavaScript objects with executable getters. The initial object check rejects null and arrays, but it is not a sandbox for hostile objects. That distinction is worth stating in a tiny utility just as it would be in a larger service. Reading an object's properties can invoke user-defined behavior in JavaScript. This example has a data contract, not a promise to neutralize every kind of object passed by an adversarial caller.
For labels, accept nonempty basenames without path separators, surrounding whitespace or a null character. This is an organizational rule because the function does not resolve paths. It is not a path-traversal defense for another component that later opens a file. The two labels must also differ ignoring case. That conservative policy reduces ambiguity when notes move between environments with different filename conventions; it does not model every filesystem's case behavior or Unicode normalization rules.
The complete checking function
The implementation collects issue codes instead of returning immediately after the first failed declaration. This allows a caller to explain several inconsistent fields together. The codes are intentionally plain and stable enough for a small demonstration. There is no automatic correction: a string width is not silently converted to a number, an unknown garment is not guessed, and a missing artifact label is not filled using another label. Those choices preserve uncertainty for the person making the handoff.
const canvases = new Map([
['classic-shirt', [585, 559]],
['classic-pants', [585, 559]],
['graphic-tshirt', [512, 512]],
]);
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
const label = value => typeof value === 'string' && value.length > 0 &&
value === value.trim() && !['.', '..'].includes(value) && !/[\\/\u0000]/.test(value);
// Accept ordinary JSON data, not arbitrary objects with executable getters.
// Checks declarations only: never reads a file or certifies an actual garment.
export function checkDeliveryNote(note) {
if (!object(note)) return { consistent: false, issues: ['record_type'] };
const issues = [];
const expected = canvases.get(note.kind);
if (!expected) issues.push('garment_kind');
const canvas = object(note.canvas) ? note.canvas : {};
if (!Number.isInteger(canvas.width) || !Number.isInteger(canvas.height) ||
!expected || canvas.width !== expected[0] || canvas.height !== expected[1]) {
issues.push('declared_dimensions');
}
if (note.format !== 'png') issues.push('declared_format');
const artifacts = object(note.artifacts) ? note.artifacts : {};
if (!label(artifacts.editable)) issues.push('editable_label');
if (!label(artifacts.export) || !/\.png$/i.test(artifacts.export)) issues.push('export_label');
if (label(artifacts.editable) && label(artifacts.export) &&
artifacts.editable.toLowerCase() === artifacts.export.toLowerCase()) {
issues.push('label_collision');
}
return { consistent: issues.length === 0, issues };
}
The function uses exact integer dimensions for the declared kind and a lowercase png discriminator. That strict discriminator is a schema choice, while the filename suffix check accepts case variations. These decisions could be changed in a different contract, but they should be documented together rather than emerging accidentally from string coercion. Notice that a label ending in PNG proves only that the label has that ending. No magic bytes, decoded pixels, transparency or garment surfaces have been inspected.
Exercise decisions with local fixture tests
Each test below changes a small part of a freshly constructed example. Rebuilding the input avoids one test's edits leaking into another. The fixtures cover three consistent declarations, dimension mistakes, a string dimension, unsupported kind, format mismatch, colliding labels, missing labels, a non-PNG export label, a path-like label, missing canvas, null, an array and a no-mutation check. Those are the cases exercised here; they are not evidence of exhaustive correctness for all possible JSON input.
Save the function as check-delivery-note.mjs and the tests as check-delivery-note.test.mjs in the same directory. Run node --test check-delivery-note.test.mjs using a Node.js version with the built-in test runner. No package installation or account is needed for these local tests. The run used while preparing this article passed sixteen tests with zero failures. Timing is not a benchmark, and the test command does not contact Roblox or the associated website.
import test from 'node:test';
import assert from 'node:assert/strict';
import { checkDeliveryNote } from './check-delivery-note.mjs';
const example = () => ({ kind: 'classic-shirt', format: 'png',
canvas: { width: 585, height: 559 },
artifacts: { editable: 'outfit.project', export: 'outfit.png' } });
const fails = (value, code) => assert.ok(checkDeliveryNote(value).issues.includes(code));
test('consistent classic shirt declaration', () => assert.equal(checkDeliveryNote(example()).consistent, true));
test('consistent pants declaration', () => { const x = example(); x.kind = 'classic-pants'; assert.equal(checkDeliveryNote(x).consistent, true); });
test('consistent graphic declaration', () => { const x = example(); x.kind = 'graphic-tshirt'; x.canvas = { width: 512, height: 512 }; assert.equal(checkDeliveryNote(x).consistent, true); });
test('wrong declared width', () => { const x = example(); x.canvas.width = 584; fails(x, 'declared_dimensions'); });
test('wrong declared height', () => { const x = example(); x.canvas.height = 558; fails(x, 'declared_dimensions'); });
test('string dimensions are not silently coerced', () => { const x = example(); x.canvas.width = '585'; fails(x, 'declared_dimensions'); });
test('unknown garment is unsupported by this small contract', () => { const x = example(); x.kind = 'unknown'; fails(x, 'garment_kind'); });
test('format discriminator is explicit', () => { const x = example(); x.format = 'jpeg'; fails(x, 'declared_format'); });
test('artifact role labels cannot collide ignoring case', () => { const x = example(); x.artifacts.editable = 'OUTFIT.PNG'; fails(x, 'label_collision'); });
test('editable label required', () => { const x = example(); delete x.artifacts.editable; fails(x, 'editable_label'); });
test('export label must end in png', () => { const x = example(); x.artifacts.export = 'outfit.jpg'; fails(x, 'export_label'); });
test('labels are basenames, not paths', () => { const x = example(); x.artifacts.editable = '../outfit.project'; fails(x, 'editable_label'); });
test('missing canvas is handled', () => { const x = example(); delete x.canvas; fails(x, 'declared_dimensions'); });
test('null is not a record', () => fails(null, 'record_type'));
test('array is not a record', () => fails([], 'record_type'));
test('checks do not mutate parsed JSON input', () => { const x = example(); const before = JSON.stringify(x); checkDeliveryNote(x); assert.equal(JSON.stringify(x), before); });
The no-mutation test compares the serialized input before and after checking it. That provides a useful regression for this fixture and this plain JSON shape. It should not be inflated into a claim that the function deeply freezes arbitrary objects or prevents changes by other code. Similarly, rejecting one path-like label does not establish a complete filename-security policy. Small tests are most useful when the accompanying claim stays close to what each test actually observes.
Keep failure messages actionable without claiming more
A caller can map declared_dimensions to a message asking the creator to review the garment kind and size declarations. It should not automatically resize an image or change a garment type to make the message disappear. A label_collision result should prompt a distinction between the editable artifact and export, not deletion or renaming of files without review. The function returns observations about the record; applying changes belongs to another, explicitly designed workflow.
Keep the original note available while correcting it. If someone changes the declared garment after a review, record the reason rather than pretending the earlier note never existed. A simple version or change note can be sufficient. There is no need to introduce a database or a complex release system merely to make a small handoff look formal. The amount of process should match the number of artifacts and the people who actually need to understand them.
Add other checks as separate layers
If real files later need inspection, put that work in a separate component with its own resource limits and claims. A header reader, image decoder and renderer answer different questions from this note checker. File identification does not establish that an image is uncorrupted, and decoding an image does not establish ownership or fitness for a garment. Keeping these layers separate makes it easier to explain a failure without converting every technical observation into a publishing verdict.
The same separation applies to preview and publication. An R6 preview can help inspect the displayed avatar, but it does not certify every avatar configuration or moderation outcome. Roblox upload procedures, possible charges, artwork permissions and moderation remain outside this utility. A successful local fixture run does not change those boundaries. It only gives a concrete starting point for discussing how declarations, artifacts and review responsibilities can stay distinct in a developer's workflow.
Disclosure: this original article and code were prepared with AI assistance for a project-associated resource. The example labels and records are hypothetical. No customer outcome, real clothing export test, official endorsement or accepted Roblox upload is claimed.
Top comments (0)