TeamcenterKnowledge

Skills

TC AWC Stylesheets

Skill tc-awc-stylesheets. Author, register and verify Active Workspace style sheets (XRT / XML Rendering Templates) on Teamcenter - putting custom content on an Overview or Details tab, working out which style sheet a surface actually resolves, carrying real markup via an SWF module htmlPanel, and the traps where a write returns 200 and changes nothing. Use for any AWC style sheet, XRT, summary panel, custom dashboard, declarative view, or "my panel does not appear" investigation.

"Style sheet" in Active Workspace means XRT, an XML Rendering Template, not CSS. XRTs live in XMLRenderingStylesheet datasets, are read from the database on every render, and control the secondary work area (the Overview / summary panel) plus the Create / Save As / Revise panels. A change lands with no WAR rebuild, no kit build and no BMIDE. CSS theming is a different animal and lives in the SWF dev environment.

Verified against TC 2606 on the TC2606 VM. See tc-vm-operations for reaching the tier and tc-soa-session for connecting.

Start here: the resolution chain is the whole problem

The single most expensive mistake is registering a correct style sheet against a type the surface never consults. It presents as "my panel does not appear", and every subsequent theory (the markup, the anchor, the module, the code page) is wasted effort. Establish which preference the surface reads before touching content.

Measured on 2606, the surfaces differ:

What is selected on screen Preference AWC resolves OOTB value
BOM occurrence in the Content tab AWC_Awb0DesignElement.SUMMARYRENDERING Pmg1CPGAwb0DesignElementSummary
Generic occurrence AWC_Awb0Element.SUMMARYRENDERING Awb0ElementSummary
Item revision opened directly (lands on Details) AWC_ItemRevision.showObjectLocation.SUMMARYRENDERING Awb0ItemRevSummaryForShowObjectLocation
Item revision, generic fallback AWC_ItemRevision.SUMMARYRENDERING Awp0ItemRevSummary

Two consequences that cost a full session each:

  • The Content tab selects an OCCURRENCE, not an ItemRevision. Registering on ItemRevision and testing in a structure means the client never reads your registration.
  • Specificity wins. The bare AWC_ItemRevision.SUMMARYRENDERING loses to any location-qualified name that already exists (AWC_<Type>.<location>.<sublocation>.SUMMARYRENDERING). Registering the least specific name and seeing no change is not evidence your XRT is broken.

Registration is the hierarchical preference AWC_<Type>.<SUMMARY|CREATE|SAVEAS|REVISE>RENDERING, with POM inheritance up the type tree.

The full precedence chain

Documented in the Customization book under "Assign style sheets to a workspace". Most specific wins, and a workspace-qualified name outranks every non-workspace name:

AWC_<workspaceId>.<type>.<location>.<sublocation>.<stylesheetType>
AWC_<workspaceId>.<type>.<location>.<stylesheetType>
AWC_<workspaceId>.<type>.<stylesheetType>
AWC_<type>.<location>.<sublocation>.<stylesheetType>
AWC_<type>.<location>.<stylesheetType>
AWC_<type>.<stylesheetType>                          <- the one everyone reaches for first

A vanilla 2606 tier already ships workspace-qualified entries (AWC_TcCAEWorkspace.ItemRevision.*, AWC_TcPhysicalTestEngineerWorkspace.ItemRevision.*, AWC_TcXSimplifiedWorkspace.*), so check whether one covers the workspace your test user resolves to. A user whose group has no workspace mapping falls through to the AWC_Default_Workspace preference.

Enumerate what already exists before you register anything

This is the step that ends the guessing. Export the site preferences and grep:

preferences_manager -u=infodba -p=infodba -g=dba -mode=export -scope=SITE -out_file=C:\kits\prefs.xml

★ The flag is -out_file, not -file; -file errors with The output file (parameter '-out_file') is not specified. Then grep the export for name="(AWC_[^"]*RENDERING)" and read the <value> out of each block. On this tier that surfaced AWC_ItemRevision.showObjectLocation.SUMMARYRENDERING and AWC_ItemRevision.showObjectLocation.OccurrenceManagementSubLocation.SUMMARYRENDERING already present and already outranking the name being set. Also grep AWC_Awb0 for the occurrence types.

How to find which one a surface actually uses

Do not reason about it. Two grounded methods, both cheap:

  1. Match the tab list. Open the candidate OOTB XRT and compare its tab titles against the tabs visible in a screenshot of the surface. Pmg1CPGAwb0DesignElementSummary was identified this way: its Overview / Markup / Weight and Balance / Cost / Parameters list matched the screenshots exactly, and Awp0ItemRevSummary did not.
  2. Capture the client's own call. The internal op is Internal-AWS2-2016-12-DataManagement/getDeclarativeStyleSheets (NOT getStyleSheet, which exists at that version but faults 214086 on every guessed struct). See tc-capture-awc-calls. It returns only the PAGE LIST even with processEntireXRT:true; section content comes from a separate sublocation load.

Where the OOTB sources live on the tier

Verified present on the 2606 VM, 2026-08-07:

C:\apps\PLM\tc_root\install\aws2\data\                    171 .xml style sheets
C:\apps\PLM\tc_root\install\aws2\data\Awp0ItemRevSummary.xml
C:\apps\PLM\tc_root\install\aws2\data\import_aws2_stylesheets.txt   OOTB manifest
C:\apps\PLM\tc_root\install\pmg1cpmgmtaw\data\Pmg1CPGAwb0DesignElementSummary.xml

Per-module style sheets live under their own module's data directory, not in aws2\data. Search tc_root\install recursively rather than assuming.

Awp0ItemRevSummary.xml is 462 lines, UTF-8 with BOM, and is itself a composition of roughly 30 <inject type="dataset"> blocks plus one <inject type="preference">. Patch it textually, not by an XML round trip, so the admin's diff is one line.

Getting real markup onto the panel

<inject type="dataset"> of an HTML dataset is not a path on 2606. Every OOTB "HTML dataset" is a one-line pointer to a declarative view from a built SWF module: Rb0DashboardReportViewer.html is 51 bytes and contains only <aw-include name="Rb0DashboardReport"></aw-include>. There is no raw-HTML route into an Overview tab.

The supported route is htmlPanel declarativeKey=, backed by a real SWF module build. Proven working on 2606 and re-verified in pixels on 2026-08-07 with the tier healthy: a custom coverage dashboard rendered on the Overview tab of a selected occurrence, styled by AWC's own theme, with its bars, burn-down and table intact. Render time tracks tier health directly: 13 s starved, 7 s part-fixed, 2 s healthy.

★★★ SETTLED by build + render: THREE selection accessors all work. .properties. vs .props. was a false dichotomy.

Measured 2026-08-10 by shipping a five-row probe in the module view and rendering it on 000241/A;1-FuelControlUnit:

1  subPanelContext.selected.properties.object_string.uiValue  = [000241/A;1-FuelControlUnit]   RESOLVES
2  subPanelContext.selected.properties.awb0ArchetypeId.dbValue = []                            (not a property of this type)
3  subPanelContext.selected.props.object_string.uiValue       = [000241/A;1-FuelControlUnit]   RESOLVES
4  ctx.selected.properties.object_string.uiValue              = [000241/A;1-FuelControlUnit]   RESOLVES
5  subPanelContext.declarativeKeyContext                      = []                             (no context= on this htmlPanel)

★★ .props., .properties. and ctx.selected.properties. all resolve. A long-running hypothesis held that the Customization book's .properties. was the correct accessor and .props. was why binding "did not work". Both work, so the accessor was never the problem and switching to .properties. fixes nothing.

★ Rows 2 and 5 are empty correctly, and they are the controls that make the positives readable: awb0ArchetypeId is an occurrence property that this revision type does not carry, and this particular htmlPanel declares no context= attribute. Without those two, five resolving rows would have told you nothing about whether the probe could produce an empty at all.

What the earlier all-empty readings actually were: AWC's "Restore from where you left off?" banner leaving nothing selected, which empties every selection accessor on the page including OOTB ones. A wrong-accessor theory and a no-selection artifact look identical, and the theory survived two sessions because nobody produced a positive control.

⚠ Binding the IDENTITY is solved. Binding the DATA is not.

The view is selection-bound; the view-model DATA is not. data.* comes from <Key>ViewModel.json, baked at module build time, so the same numbers render for every object of that type: the coverage dashboard showed the InvertedFlight ledger while the selected occurrence was an unrelated additive_target_component_metric_2606, and showed it again on that item opened standalone.

So a panel can correctly name the object it is on while reporting another object's numbers, which is worse than an obviously generic panel. Per-object data needs the view model to fetch using the bound id - a dataProviders / SOA action keyed off subPanelContext.selected.props.item_id.dbValue - not a different accessor. State the snapshot's scope and date on the panel itself until that exists.

An injected XRT MUST be rooted in <subRendering>, never <rendering>:

<subRendering>
  <content visibleWhen="item_id == NNN">
    <section title="...">
      <property name="object_string"/>
      <htmlPanel declarativeKey="..."/>
    </section>
  </content>
</subRendering>

Put a <property> beside the htmlPanel while testing. It splits one ambiguous failure into two distinguishable ones in a single run:

  • section absent entirely -> the registration is still pointed at the wrong type
  • section shows the property but not the panel -> XRT is live, declarativeKey is not resolving
  • section shows both -> done

Remove the diagnostic property before any demo.

visibleWhen on the injected content is what keeps a site-scoped registration from changing every other object's Overview tab.

★★ Not every relation reaches the XRT layer at all, by property OR by objectSet

A type-specific GRM relation does NOT reliably compile into a same-named reference property, and where it does not, an <objectSet> over it does not work either. Measured end to end on Seg0Exhibits (Mission Engineering session, cloud2506 then TC2606):

Route Result
<property name="Seg0Exhibits"/> renders nothing
getProperties on the revision, under Seg0Exhibits, seg0Exhibits, Seg0FtCapability, IMAN_Seg0Exhibits 200 with empty props
getProperties on the relation uid itself InternalServerException
<objectSet source="Seg0Exhibits.Seg0FtCapability"> in a rendered tab section renders, zero rows
expandGRMRelationsForPrimary / ForSecondary works

The objectSet was verified on 000774, an object with three capabilities confirmed by expandGRMRelationsForPrimary, on a pool 23 minutes warm. So this is a real negative, not an empty data set and not a caching artifact.

★★ So Seg0Exhibits does not expose to the XRT layer at all. The only route is expandGRMRelations, which means an htmlPanel backed by a module that calls it itself - not a fallback, the only option. Selection binding makes that viable: the panel can read subPanelContext.selected.props.item_id.dbValue and traverse from there.

Do not read an empty section as a style-sheet fault before checking the data exists. On the same tab two other sections rendered empty purely because no Att0HasParamValue, Seg0Refine or Seg0Satisfy had been authored on those objects. Three of four sections were correct; only the relation-backed one was defective. Confirm the relation is populated with expandGRMRelations before blaming the sheet.

Building the SWF module that backs the htmlPanel

The dev environment ships complete on the tier at C:\apps\PLM\tc_root\aws2\stage (initenv.cmd, awbuild.cmd, node_modules already installed, 133 OOTB kits under src/repo). Nothing to set up.

awbuild.cmd is the whole pipeline: createNodeModules -> audit -> build -> publish, and it publishes to the gatewayURL in stage/tem.properties. About 45 minutes on this VM.

Minimal module an XRT htmlPanel can call, copied from OOTB AddProjectCommandXrt in projmgmt:

src/<kit>/module.json    {name, description, pathOffset:".", version, author}
src/<kit>/kit.json       {name, description, modules:[<name>], version}
src/<kit>/src/assets/html/<Key>View.html
src/<kit>/src/assets/viewmodel/<Key>ViewModel.json     can be just {"schemaVersion":"1.0.0"}

Then <htmlPanel declarativeKey="<Key>"/> resolves <Key>View.html.

The doc says src/html and src/viewmodel. The real repo uses src/assets/html and src/assets/viewmodel. Copy the repo, not the book.

★ Plain HTML with inline styles works inside the view; OOTB views wrap content in <div class="sw-row"><div class="sw-column w-12">. Avoid {{ }} in generated markup, it is template binding syntax.

★★ Run the build as a scheduled task. Start-Process launched from a WinRM session dies with the session and leaves no log at all, which looks exactly like a build that never started.

★★★ The AUDIT runs before build and publish, and it is the cheapest check you have

awbuild.cmd runs audit first. A bad kit fails in 90 seconds to 2.5 minutes and nothing is built and nothing is published, so the previously published site stays live and untouched. Run it deliberately as a syntax-and-dependency check before you consider a module finished, rather than discovering problems after a 25-minute build.

Three audit rules that reject perfectly good code, all hit on 2026-08-10:

★★ Non-ASCII characters in src/assets/js fail no-irregular-whitespace. The star and warning glyphs used liberally in comments in this workspace will fail the build. Plain ASCII in JS.

★★ no-undef fires even on an identifier guarded by typeof x !== 'undefined'. An AMD module calling a sibling it did not declare is an error, not a warning. Declare it: define(['js/Xc0Other'], function (Xc0Other) {.

★★★ js-dep-check cannot see exports through an AMD define() wrapper, and reports "The method X is not exported" about a file where X IS exported. Verified by reading the staged file - one define, function classifyThread present, classifyThread: classifyThread in the returned object - and the check still failed.

The OOTB modules that the check resolves use ES module exports, confirmed by following a shipped view model's deps to its file:

"deps": "js/utils2dViewer"   ->   2d-viewer\src\assets\js\utils2dViewer.js
                                  export let setUsePictureSlinging = function( value ) { ...

So a view-model JSFunction action whose deps names an AMD module will fail the audit no matter how correct the module is. ⚠ One OOTB sample is strong evidence that ES exports work and only suggestive that AMD never does. The error text offers jsDepCheckMethodNotExistWhitelist in staticCodeAnalysis_dev.json, which exists precisely because this check false-negatives.

Retracted before it spread: "no-undef cascades into a false not-exported report." I asserted that from the two errors appearing together, and relayed it to another session as a finding. Fixing no-undef left the export error standing alone. They were independent, and the export error was the real one. Two errors arriving together is not evidence that one causes the other.

The build source is on the TIER, not in your repo. stage\src\<kit>\src\assets\... is what awbuild reads. A local out/awc/module/ copy can be committed, correct and completely irrelevant to what ships. Stage first, then build.

Verify the module actually shipped, rather than trusting the build log alone:

Check Where
module was seen + <kit> line in the build log
view was bundled out/site/static/js/dynamic-<kit>.*.chunk.js exists and contains your markup
key is resolvable out/pathMap.json maps <Key> to src\<kit>\src\assets

★★★ RETRACTED: "The requested page does not exist" was a WRONG URL, not a workspace grant

This section previously asserted "Workspace contributions are CONFIG BUILDER data, not build input" and told you to reach Config Builder to grant pages. That conclusion is withdrawn: it was never tested, and the symptom it was built to explain had a different cause.

What actually happened on TC2606, 2026-08-08. The four AWC Reports pages 404'd with The requested page does not exist. I diagnosed a missing workspace grant, edited workspace_contribution_reportsjs_TcAuthorWorkspace.json, ran a full build + publish (RC=0, 33 minutes, tier starved to 111 MB free), restarted the pool, and the pages still 404'd. I concluded the JSON was inert.

Then I opened the UI and clicked the Go to Reports link. It worked immediately, and the address bar read #/showMyDashboard. Every 404 had come from requesting the module-qualified form #/com.siemens.splm.reports:showMyReports. The route is the bare page id. The grant was never missing and the rebuild was never needed.

Two separate lessons, and the second is the one that cost the outage:

  • ★★★ A 404 on an AWC location route is a wrong-URL hypothesis first, a configuration hypothesis last. Click through the UI once and read the URL off the address bar. That is one click against a 33-minute disruptive rebuild on a shared tier.
  • ★★★ I never tested the correct URL either before or after the rebuild, so I cannot say whether the contribution edit had any effect. The edit is therefore unproven in both directions - neither "inert" nor "works". Do not cite this section as evidence about how workspace contributions behave, because it contains no such evidence. If you need to know, test it deliberately with a route you have already confirmed by hand.

One further measurement taken while cleaning up, which is grounded and worth having. On TC2606 today, src/solution/workspace_contribution_reportsjs_TcAuthorWorkspace.json is unmodified OOTB (mtime May 2026) and grants exactly one page:

{ "schemaVersion": "1.0.0", "workspaceId": "TcAuthorWorkspace",
  "availablePages": [ "createReportTemplate" ] }

...and infodba reaches #/showMyDashboard, #/showReportBuilderReports and #/showReport anyway. So either infodba does not resolve to TcAuthorWorkspace, or that per-module file is not what gates these routes. Both remain open. What is settled is only that the 404 was the URL form.

The one thing in the original section that IS grounded and still holds is the availablePages file shape and the warning below about switching workspaces wholesale.

The in-product Config Builder route (Customization book, Modify an existing workspace: switch to Active Architect, Workspace tab, edit availablePages, Save) is a real documented mechanism. It was simply not needed here, and was never exercised.

Related trap when picking a workspace to switch to: compare what it grants against what you have. On this tier TcQualityManagementWorkspace grants the full reports page set, but switching the site default to it would have lost 85 pages to gain 5 (easyplan, assemblyPlanning, compareRevisions, dispatcherRequests among them). Count both directions before switching.

A workspace contribution is NOT required for a view-only module, and adding one is cargo cult. src/solution/workspace_contribution_<module>_<Workspace>.json has the shape {schemaVersion, workspaceId, availablePages:[...]}: it grants a workspace access to pages (locations / sublocations). A module that only supplies a view invoked by declarativeKey declares no pages, so there is nothing legitimate to list. Verified by reading the OOTB files, after this was wrongly proposed as the fix for a panel that was actually a registration problem.

Chunk filenames on this tier contain efbfbd (the hex of U+FFFD) for ~6000 of 6252 chunks. That is deterministic, not corruption. Two builds under different accounts (SYSTEM, then Administrator with chcp 65001) produced byte-identical names. Do not chase it and do not blame a missing panel on it.

Creating the datasets

Use the shipped utility, not createDatasets over SOA. Documented in the Utilities Reference and batchable via utility_execution_set:

install_xml_stylesheet_datasets -u=infodba -p=infodba -g=dba -input=<manifest.txt> -filepath=<dir> [-replace]

Manifest lines are datasetName, file.xml. It sets tool_output_formats "ASCII" and file type SS_TEXT, provenance the SOA create path never applies. Run it inside the TC environment shell (tc_root\tc_menu\tc_Vanilla_Env.bat); TC utilities exit 0xC0000135 having done nothing if the profile vars are not sourced first. It does not install HTML datasets; those ship with the module.

Verified named references, read off OOTB datasets rather than documented next to the type:

Dataset type Named reference File
XMLRenderingStylesheet XMLRendering <name>.xml
HTML HTML <name>.html

Two saved-query matches per dataset name is NORMAL (the dataset plus its version object); OOTB Awp0ItemRevSummary returns 2 as well. Do not read it as a duplicate.

See tc-datasets-files for the SOA dataset route when you genuinely need it.

Registering: two silent failures

★★ setPreferencesAtLocations' argument is named setPreferenceIn, not preferenceInputs. A wrong TOP-LEVEL argument name returns HTTP 200, empty ServiceData, no partialErrors, and no effect. The struct is:

[{ location: {object: NULLTAG, location: "Site"},
   preferenceInputs: {preferenceName, values: []} }]

where the inner preferenceInputs is a single struct, not an array. Read the argument name off PreferencemanagementService.hxx in the SOA client kit rather than guessing. See tc-soa-payload-shapes for the general class of this failure.

★★ Preference reads are cached per SOA session. After a successful write, getPreferences on the SAME session still returns the OLD value. Disconnect and reconnect before reading back. This has twice produced a false "the write failed" conclusion.

Reconnecting is necessary but NOT always sufficient - see the pooled-tcserver finding below. There are two distinct caches (session and pool) and they are not the same problem.

★★★ A preference written with preferences_manager does not reach POOLED tcservers, and a new session is not enough. Measured on TC2606, 2026-08-07, registering a summary style sheet:

  • preferences_manager -mode=import -scope=SITE -action=OVERRIDE reported success.
  • preferences_manager -mode=export -scope=SITE immediately showed the NEW value, with zero occurrences of the old one anywhere in the file. The database was correct.
  • Every SOA session, including brand new logins, kept returning the OLD value. So did Active Workspace. Reconnecting did not help, because the stale copy lives in the pooled tcserver processes, not in the session.
  • Administration-2012-09-PreferenceManagement/refreshPreferences2 faulted.
  • Only Restart-Service "Teamcenter Server Manager TCDB_PoolA" -Force in the guest picked it up. Immediately after, a fresh session read the new value and the tab rendered.

So exporting a preference to verify it is necessary and NOT sufficient: the export reads the database, the client reads the pool. Budget a pool restart into any style-sheet registration, and warn other sessions on a shared tier first, since it drops every TC session and returns Active Workspace to the login page.

⚠ OPEN QUESTION: this does not reproduce for every preference, and the difference matters

The finding above was measured on a style-sheet registration (AWC_<Type>.SUMMARYRENDERING). A different session, same tier, same day, measured the opposite for two other writes, and both observations are real:

Write Preference Took effect without a pool restart?
preferences_manager -mode=import -scope=SITE -action=OVERRIDE Xc0PortAuditEnabled (read by a visibleWhen="{pref:...}" on an XRT <page>) YES - flipped to false, the tab vanished from the render; flipped back to true, it returned. No restart either time.
setPreferencesAtLocations (SOA) AWC_Fnd0LogicalBlockRevision.SUMMARYRENDERING YES - a fresh session rendered the new host immediately.
preferences_manager -mode=import AWC_<Type>.SUMMARYRENDERING NO - stale in every session until the pool restarted.

So "a pool restart is the only thing that picks it up" is too broad as written. What is not yet separated:

  • whether the write path matters (SOA setPreferencesAtLocations vs preferences_manager), or
  • whether the preference's role matters - a style-sheet REGISTRATION looked up once per render context and cached in the pool, versus an ordinary value read fresh when visibleWhen evaluates.

The second is the better hypothesis on the evidence, because a preferences_manager write to Xc0PortAuditEnabled propagated fine while a preferences_manager write to a SUMMARYRENDERING did not. Do not generalise either way from one preference. Verify the specific preference you changed actually reached the client, and keep a pool restart in reserve for registrations.

⚠ A "protectionScope resolves this" answer was published on 2026-08-08 and is RETRACTED

For about an hour this file claimed the open question was answered: that protectionScope="User" was the whole cause, that a Site-scoped registration needs no restart, and that "budget a pool restart" was too broad. That was wrong. The caching finding above stands. The retraction is kept because the way it went wrong is more useful than the claim was.

  1. A SUMMARYRENDERING registration verified correct by export would not render. A marker test - renaming a tab to Overview-XC0MARK in the host - proved the host was not being served at all, ruling out markup, visibleWhen, tab order and the overflow chevron separately. That part is solid and is the useful technique here.
  2. Re-registering at Site scope made the tab appear immediately, with no restart. Conclusion drawn: scope was the cause.
  3. Confounded. The VM had been restored from a checkpoint ~50 minutes earlier and Teamcenter Process Manager restarted ~20 minutes earlier, so the pooled tcservers held a cold preference cache. The write landed before anything had cached the old value. Two variables moved and I credited the wrong one.
  4. Falsified by a controlled test. Both registrations were pointed at a different host (Xc0ScftaOnly, which deliberately omits the Port Audit inject) and verified by export as protectionScope=Site, value Xc0ScftaOnly. The client went on rendering the Port Audit tab, which exists only in the other host. A correct, Site-scoped, verified write was invisible.

★★★ So: exporting a preference proves the DATABASE, never the client. Budget a pool restart into any style-sheet registration, or accept the change is invisible until something recycles the pool.

★★★ SETTLED: the tcserver pool caches PARSED style-sheet content. Every change needs a recycle.

Answer five, and the only one from a pre-registered controlled test. Read this one and skip the archaeology below it.

Method, because the method is why this one can be trusted:

  1. Renamed one tab in the host dataset to Trade V2CHECK. Installed with -replace on a 26-minute warm pool.
  2. Read the dataset back out of Teamcenter through FMS and confirmed the newest version (Xc0LBRevSummaryPortAudit;7) held the marker, identical after newline normalisation. So the database provably had it.
  3. Rendered on a 29-minute warm pool: marker absent.
  4. Wrote the interpretation down before the restart, and another session recycled the pool with zero pid overlap and deliberately issued no HTTP to AWC, so my render was the first request the new pool saw.
  5. Rendered again: marker present.
Outcome Meaning Result
marker appears after recycle pool cached parsed content THIS
marker still absent after recycle client resolves a non-newest dataset version ruled out

Conclusions, both directions:

  • ★★★ The pool caches parsed style-sheet content. A dataset content change is invisible to every client until Teamcenter Server Manager TCDB_PoolA recycles - just as a preference value change is. Budget a pool recycle into any style-sheet change, content or registration.
  • ★★ install_xml_stylesheet_datasets -replace IS a safe deploy verb. It adds a version, and the newest version is what gets served after a recycle. The "-replace silently did nothing" reading that recurs in this file was always the cache, never the utility.

Verify a deploy in two independent steps, because they fail differently. First assert the DATABASE holds your bytes: fetch the stored file through FMS and diff. Then assert the CLIENT renders it, after a recycle. The utility's own output tells you neither - a replace logs no per-dataset line at all and exits 0 either way.

Normalise newlines before diffing. install_xml_stylesheet_datasets rewrites LF to CRLF, so a byte comparison of a stored dataset against your generated file reports a false mismatch on a clean deploy - which reads exactly like the silent-no-op above.

Read the HIGHEST dataset version, never the un-suffixed row. The row whose object_string carries no ; is not the newest; measured elsewhere it shared its ImanFile with ;3 while ;5 held the fresh content. And getFileReadTickets needs files with the ImanFile uid from ref_list; passing datasetFiles returns tickets:[].

⚠ Superseded: ASSUME EVERYTHING IS CACHED UNTIL THE POOL RECYCLES

A "register once, then only change CONTENT" rule was published here on 2026-08-08 and is RETRACTED the same day. It claimed dataset content changes are not cached and so need no restart. The one clean test contradicts it.

Change Cached? Evidence
Preference value (host A -> host B) YES Registrations repointed to Xc0ScftaOnly, Site scope + new value verified by export. Client kept serving the old host's tab. Cache temperature irrelevant - this test does not depend on it.
Dataset content of the registered host UNPROVEN, evidence says cached Content-only change (renaming a tab to Trade WARMTEST) installed on a warm pool: did not appear. The earlier "it appeared instantly" observation was taken 4 minutes after a pool restart.

The timestamps, because the whole lesson is in them:

pool restarted            13:26:44      (by another session, mid-experiment)
tcservers started         13:27:09
"three tabs appeared!"    13:30:53      <- 4 min into a COLD pool. Confounded.
content-only change       13:36:31      <- warm pool
render                    13:39:42      <- marker did NOT appear

★★ Two candidate mechanisms remain and I did not separate them: the pool caches parsed stylesheet content, or install_xml_stylesheet_datasets -replace adds a dataset VERSION and the client keeps serving an older one. Both predict what was seen. Do not assert either.

Operationally, until someone separates them: treat every style-sheet change - preference OR dataset content - as invisible until the tcserver pool recycles. That is the conservative reading and it costs nothing but a restart you were probably budgeting anyway.

★★ This also dissolves the multi-session collision. One type has one summary style sheet and a second registration REPLACES rather than merges, so two sessions racing the same preference means the loser's tab vanishes with no error. Composing into one host fixes it: three sessions' tabs now ship from a single registered host on this tier, each as its own <inject>:

<page title="Port Audit">...</page>            <!-- injected file is rooted <subRendering><page> -->
<inject type="dataset" src="Xc0ScftaTab"/>
<page title="Trade">                            <!-- injected file is rooted <subRendering><content> -->
    <inject type="dataset" src="Msn0SolutionClassTradeTab"/>
</page>

Two injected-file shapes exist and they nest differently. A file rooted in <subRendering><page> contributes its own tab: inject it at top level. A file rooted in <subRendering><content> is a fragment: the HOST must supply the <page> wrapper around the inject. Getting this backwards double-nests or contributes nothing, silently.

★★ The inject attribute is src=, not name=. OOTB uses src throughout. A handover package here specified name=, which does not resolve and produces exactly the silent no-tab outcome this file is full of.

Tab order is document order, and AWC pushes surplus tabs behind an overflow chevron most users never open. With 11 tabs on this host the chevron is live, so placement is a real decision, not cosmetic. Probe the rendered tab list rather than assuming - [role="tab"] labels off the live page.

★★★ protectionScope is DISPROVEN as a factor, not merely untested

Upgraded from "untested" to disproven later the same day, on a counting argument that needs no recycle. SITE export of TC2606:

*.SUMMARYRENDERING preferences     1,803 total
    protectionScope="User"           959
    protectionScope="Site"           844

AWC_ItemRevision.SUMMARYRENDERING  protectionScope="User"   <- and every ItemRevision renders fine

959 of 1,803 are User-scoped, including the OOTB registration every ItemRevision on the tier renders through. If a User-scoped SUMMARYRENDERING were silently unresolvable, most summary panels on the tier would be blank. They are not.

★★ What protectionScope actually means: the LOWEST scope at which a preference may be overridden - not the scope the effective value lives at. A User-scoped preference is one a user is permitted to override; the site value still resolves underneath. So it was never a gate on resolution at all.

Do not Site-scope a registration and recycle in the same pass, or you will credit the wrong half again. That is precisely how this wrong mechanism was born: a Site-scoped rewrite coincided with a pool recycle, the tab appeared, and scope took credit that belonged to the cache.

Everything on this surface reduces to the one cache: registration changes and dataset content changes are both invisible until TCDB_PoolA recycles. One mechanism explains all of the evidence, which is a cleaner story than the two-gate model that briefly replaced it.

Historical note, kept because the shape recurs: this section previously read "whether protectionScope matters as well is now untested - it may be a real second factor". That hedge was too generous. The scope observation was a true reading of the tier offered by another session, but the mechanism implied by it was never verified, and it displaced a correct hypothesis for two hours. Same failure as everywhere else in this file: the observation was clean, the cause bolted onto it was a guess.

★★★ The generalisable trap, and the reason this is worth reading: a cold cache after a restart makes a broken write look fixed. If a change starts working right after any restart, revert it and re-apply it on a warm system before believing the fix. This produced a confidently-wrong conclusion that was committed, deployed to a live site, and relayed to another session, all within the hour.

★★★ It then happened AGAIN, the same afternoon, to the replacement conclusion. A second session restarted the pool at 13:26:44 while this experiment was open and told me by a message that queued behind an in-flight turn. I installed and rendered at 13:30, saw new tabs appear, and published "dataset content is not cached". Same confound, same cause, four minutes apart. Three published conclusions in one afternoon were all confounded by restarts I did not know about.

Two rules came out of it, and they are worth more than any of the retracted findings:

  • For the operator: do not perform an infrastructure operation on a shared tier while another session has an experiment open, until that session has acknowledged. A queued message is not an acknowledgement. If it is urgent, do it and say so loudly, so the other session can discard measurements from that window rather than publish them.
  • For the experimenter: timestamp your own measurements and ask what restarted recently before publishing a mechanism. (Get-Process tcserver | Sort-Object StartTime | Select -First 1).StartTime gives the pool's age in one call. If your measurement is younger than the pool, it proves nothing about caching.

★★ Stop asserting mechanism from a single observation on shared infrastructure. Publish what was measured, with the pool age beside it, and label the mechanism unproven. Every wrong call here came from naming a cause after one uncontrolled run.

★★★ Pre-registering the OBSERVATION is not the same as pre-registering the CAUSE

The subtlest trap of the lot, and the one that survives all the discipline above. Pre-registration fixes what you will see; it does not validate the explanation you attach to it. A cause smuggled into a branch label rides along unverified and then looks earned, because the surrounding rigour was real.

Worked example, same tier, same day as everything above. The branch was written in advance as:

error persists on a cold pool -> a summary report requires all three ReportChartN slots

The error did persist on a cold pool. The condition was clean, answerable, and correctly predicted. The explanation was wrong: three slots were already declared, and the real fault lay in the table-column parameters. A looser process would have published "summary reports require three chart slots" on the strength of the condition being met, and it would have read as a properly controlled finding.

★★ So split them. Pre-register only what you can observe:

GOOD  branch: "does the marker render after a recycle, yes or no"      <- observable
BAD   branch: "marker absent -> the client resolves a stale VERSION"   <- cause bolted on

Derive the mechanism after the observation lands, and treat it as a fresh unproven claim needing its own test. This is nastier than an ordinary wrong guess precisely because the rigour around it is genuine, so the usual smell test ("was this controlled?") returns yes.

★ Distinction named by the TC VM session after it caught this happening.

★★ Get-Service misreports Teamcenter service state over WinRM. On that same healthy tier it listed Teamcenter Server Manager TCDB_PoolA, Teamcenter WebTier and every other TC service as Stopped while they were serving requests. Get-CimInstance Win32_Service and the process table both said Running (pool pid 14196, web tier 16252). Taken literally, the first reading says "the tier is down" and sends you to start services on a working system. Use Win32_Service and the process list. Related: tcserver processes are spawned on demand and reaped, so a count of 0 means an idle pool, not a stopped one: it read 0, then 8, then 0 within minutes.

★★★ A failed write and a failed SESSION look identical at the call site. Two preference writes returned FAIL that had not been rejected at all: the login underneath them had failed with No business server instances are available. The identical calls succeeded once the tier had memory. Check the session before recording a constraint about preferences. See the next section.

The tier must be healthy before you diagnose anything

Style-sheet symptoms and tier-starvation symptoms are indistinguishable from the browser: skeleton placeholders, an empty structure tree, a red "The server encountered an error" toast, and No business server instances are available on login.

Measured on this VM, 2026-08-07: guest memory demand 15.4 GB against an 11.77 GB cap, guest available memory down to 182 MB, and AWC login failing outright. None of it was an XRT problem.

Check before diagnosing:

Get-VM -Name TC2606 | Select-Object @{n='AssignedMB';e={[math]::Round($_.MemoryAssigned/1MB)}},
                                    @{n='DemandMB';e={[math]::Round($_.MemoryDemand/1MB)}}

If Demand exceeds Assigned the tier is paging and nothing you observe in the browser is evidence about your style sheet.

Measured either side of the fix on this VM, 2026-08-07:

starved fixed
Assigned / Demand 11.77 GB / 15.4 GB 13.9 GB / 11.8 GB
Guest available 182 MB 6069 MB
tcserver processes 24 8
AWC login No business server instances are available ok
Panel render 13 s, or skeletons 2 s

Demand below Assigned is the check that matters, not free memory in the guest.

The tcserver pool is the dominant consumer and is configurable. C:\apps\PLM\tc_root\pool_manager\confs\TCDB\serverPool.properties:

PROCESS_MAX=30          ceiling, only matters under load
PROCESS_WARM=3          warm spares
PROCESS_TARGET=0000 4   time-of-day schedule: "HHMM count"

Shipped as PROCESS_TARGET=0000 24, which held 24 warm tcservers (5.78 GB measured) on a single-user demo VM. Back the file up, edit, then restart Teamcenter Server Manager TCDB_PoolA.

Do not cut it too far. HARD_TIMEOUT_EDIT / HARD_TIMEOUT_READ default to 28800 seconds (8 hours), so a handful of held sessions pin servers all day. A target of 4 exhausted the pool immediately and reproduced No business server instances are available. 8 target / 3 warm was stable.

★★★ The trim above has NEVER actually been applied to the TC2606 VM. Do not read this section as describing the tier's current state. Checked directly 2026-08-08: serverPool.properties mtime is 07/14/2026, unmodified, carrying the shipped PROCESS_TARGET=0000 24 and PROCESS_WARM=5, and there is no backup file beside it (which the recipe above tells you to create first, so its absence is the tell). The "fixed" column in the table above was reached by some other means and did not survive; the VM has been restarted since.

So expect to find this tier starved, and expect the pool to be the reason. Measured the same day, two hours after a guest restart:

value
VM Assigned / Demand 16384 MB / 22773 MB
Guest free 465 MB
tcserver processes 24
CPU 3% (it is paging, not computing)

★★ Raising the VM's memory is NOT an available lever here, so do not reach for it first. Get-VMMemory -VMName TC2606 reports dynamic memory with Maximum = 16384 MB, which is exactly what is already assigned, and the host has 32678 MB total with only ~2092 MB free. There is no headroom to give. Reducing demand inside the guest is the only real fix.

The pool restart is disruptive and this VM is SHARED. Restarting Teamcenter Server Manager TCDB_PoolA drops every open AWC session and any in-flight utility run, and several workstreams (SCFTA, integration-readiness, reports) use this tier concurrently. Confirm before restarting it rather than treating tier health as a free unilateral fix.

★ Symptom to recognise before you blame your own work: WinRM Invoke-Command calls that ran in seconds start timing out at 2 to 5 minutes, while light commands (reading a file, listing processes) still return promptly. That split is the signature of a starved pool: anything that needs a tcserver session queues, anything that does not is fine. An aw_import_export_reports call that times out in that state has an unknown outcome, not a failed one. Re-read before re-running.

★★★ A SUCCESSFUL SOA READ DOES NOT CLEAR THE TIER. It is not a control.

The most persuasive wrong conclusion this environment produces is "the tier is fine, so the fault must be mine". It is usually reached by exactly one move: running a light SOA call, watching it succeed, and treating that as evidence the tier is healthy.

It is not. A getProperties read and a full Active Workspace render have completely different tcserver demands, which is the same split described immediately above. On a starved pool the light call returns promptly while the render 500s. So a passing SOA read cannot discriminate, and using it as a control means the tier stays "ruled out" while it is in fact the cause.

Twice in two days on TC2606 this produced a fabricated defect that was more persuasive than an ordinary mistake, because it was carefully argued from real measurements:

symptom attributed to actually
search-backed reports all returning 0 "the full-text index has stopped serving" a checkpoint restore, different database
every AWC render 500ing with Failure in loading Summary "the shoot-awc harness builds a session AWC rejects" tier starvation, demand 20.2 GB against a 16.4 GB cap

In the second case the author had ruled the tier out because their SOA calls kept succeeding, and had ruled out the objects and the style sheet correctly. Three good eliminations, one control that could not discriminate, and the conclusion landed on the only suspect left.

★★ What actually discriminates, in order of cost:

  1. Read VM memory first, before forming any hypothesis. Demand above Assigned means every observation you are about to make is unreliable. One command, no session needed.
  2. Look for a time correlation. The same operation on the same object type succeeding earlier and failing later is tier state; a structural defect cannot produce opposite outcomes on the same input ninety minutes apart.
  3. Only then blame the tool, and re-run the failing case once the tier is healthy before publishing.

And do not bank a failure on a starved tier even when it supports you. A failing render there is uninterpretable in both directions - it cannot confirm a defect and it cannot refute one.

★★ Prefer the test whose outcomes are ASYMMETRICALLY informative, and run it even under bad conditions when the informative direction is the cheap one. Here: a render succeeding on a starved tier is conclusive (a structural defect cannot succeed under worse conditions than the ones it supposedly failed in), while a render failing settles nothing. That asymmetry made one attempt on a 0.55 GB-free tier worth more than waiting a day for a clean one - and it delivered, so waiting would have bought a weaker answer, not a better one.

★★★ The re-read rule fires hardest when you are confident, which is exactly when nobody looks. The ballooning measurement above was not found by insight. A pool recycle was run expecting it to work, and guest free memory was re-read afterwards only because re-reading the artifact is the standing rule - at which point the number had gone the wrong way. Every mechanism-succeeded- effect-absent finding in this file was caught the same way. That is the case for the rule rather than a demonstration of skill: it earns its cost on the runs you were sure about.

Verifying: both kinds of evidence lie, in opposite directions

Capital_TC_Integration/bin/shoot-awc.mjs logs in over JSON REST SOA, transplants the session cookie into Edge via the DevTools protocol, and dumps a screenshot plus page text plus console errors plus requests still in flight.

node bin/shoot-awc.mjs --uid <revUid> --wait-for "<a string only your panel renders>" \
     --settle 45000 --out shot.png [--click-text "Details"] [--eval-file probe.js] [--headed]

Three failure modes worth knowing, all hit on 2026-08-07:

  • ★★ The page TEXT can be healthy while the pixels are skeletons. document.body.innerText returned the full property list and the whole dashboard while the visible layer showed grey bars. Reporting from the text dump produced a false "it renders".
  • ★★ A DOM assertion with guessed selectors is worse than no assertion. A probe counting [class*="skeleton"] returned 0 on a page that was visibly grey, because those elements do not carry that class. Sample document.elementFromPoint(x, y) and report the real class chain instead of guessing at class names.
  • ★★★ Grey blocks in a HEADLESS capture are not proof of a broken render. Sampling the DOM at those exact coordinates returned SPAN.sw-property-name containing the text "ID", transparent background, no animation. The same page rendered clean in --headed. --headed is the tie-breaker and should decide any dispute between a screenshot and the DOM.

--eval-file, not --eval: PowerShell strips inner double quotes when handing a string to a native exe, which silently turns any selector containing quotes into a JS syntax error.

--wait-for polls for a string and reports honestly whether it ever appeared, which beats a fixed settle. A fixed delay screenshots a cold tier's skeletons and looks exactly like a page that rendered nothing.

Rollback

One preference write per registration:

AWC_ItemRevision.SUMMARYRENDERING            -> Awp0ItemRevSummary
AWC_Awb0DesignElement.SUMMARYRENDERING       -> Pmg1CPGAwb0DesignElementSummary
AWC_Awb0Element.SUMMARYRENDERING             -> Awb0ElementSummary

Nothing else has to be undone; the datasets can stay. Reconnect before reading back (see the caching trap above).

⚠ Diagnostic registrations are easy to leave behind. One left on this tier was AWC_ItemRevision.showObjectLocation.OccurrenceManagementSubLocation.SUMMARYRENDERING = Xc0ItemRevSummaryV3, a patched summary whose injected section contained only the htmlPanel. Enumerate every *RENDERING preference you set and revert the scaffolding, not just the one that worked.

Retracted conclusions, kept so nobody re-derives them

"Any custom injected section that actually RENDERS hangs Active Workspace" is WRONG. So is the table concluding that <property> renders while <label> and injected HTML hang. Both were measured against a preference the client was not reading, so every variant "hung" for the same reason: the surface was never consulting that style sheet at all. Retracted by the session that found the registration bug, 2026-08-07.

Now positively disproven, 2026-08-07, with a control. A custom injected <page> carrying <section> + <property> + <htmlPanel> rendered; the identical page differing only by an added <label text="..."/> and <break/> rendered the same. <label> is fine on 2606. See tc-awc-custom-tab.

A grey "skeleton" screenshot is not evidence, and --headed does not settle it. The other half of the original mistake was the capture itself: Page.captureScreenshot with captureBeyondViewport: true re-renders into an off-screen surface that Active Workspace's virtualized panels never paint into, so a fully populated DOM captures as tidy grey placeholder bars in headless AND headed alike. Capture fromSurface: true with that flag off, and arbitrate any dispute with document.elementFromPoint rather than with the image or with guessed selectors.

"Every ItemRevision's full-page Details tab renders skeleton placeholders forever" is not reproducible once the tier has memory and the render is checked headed. The original observation is consistent with tier starvation plus the headless capture artifact above. If you see it, check memory and run --headed before concluding there is a defect in AwDetailsSublocation.

Documentation

Release-matched 2606 set (confirm a release by opening a PDF title page, the collection IDs are opaque):

Document Path
Active Workspace Customization D:\Siemens\Help Server\collections\documentation\external\PL20251212545240207\en-US\tc_help\Active Workspace Customization.pdf
Active Workspace Fundamentals same directory, Active Workspace Fundamentals.pdf
Teamcenter Utilities (for install_xml_stylesheet_datasets) same directory, Teamcenter Utilities.pdf
Teamcenter Server Customization same directory

The 2506 equivalents are under ...\external\PL20241125556497283\... and are a different release. Use 2606 for this VM.

Local help server: http://localhost:51000. pypdf extracts these PDFs cleanly, which is the way to check a utility flag claim rather than trusting a write-up.

★ The Siemens Web Framework reference and the Storybook / UI Pattern Library are NOT in the local D: help set. Configuration links out to /doc/282219420/PL20251212546352891.swf and .storybook; that collection ships only AdminDataReport / DataModelReport / ITKFunction / TcServerInterfaces. If you need SWF module or declarative-view detail, it is not on this machine.

Related skills

tc-awc-custom-tab (contributing a whole TAB rather than a section: the top-level inject pattern, tab order and the overflow chevron, visibleWhen scoping, and the capture traps), tc-vm-operations, tc-soa-session, tc-soa-payload-shapes, tc-capture-awc-calls, tc-datasets-files, tc-query-discovery, tc-soa-docs-navigation.


Generated from skills/tc-awc-stylesheets/SKILL.md in the tc-automation-skills library, which is the canonical copy and also serves as the agent skill set for Teamcenter work.